#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/wait.h>

#include <speex/speex_echo.h>
#include <speex/speex_resampler.h>

#include "conf.h"
#include "audio.h"
#include <time.h>
#include <speex/speex_preprocess.h>

const char *usage =
    "Usage:\n %s [options]\n"
    "Options:\n"
    " -i PCM            playback PCM (default)\n"
    " -o PCM            capture PCM (default)\n"
    " -r rate           sample rate (16000)\n"
    " -c channels       recording channels (2)\n"
    " -b size           buffer size (262144)\n"
    " -d delay          system delay between playback and capture (0)\n"
    " -f filter_length  AEC filter length (2048)\n"
    " -s                save audio to /tmp/playback.raw, /tmp/recording.raw and /tmp/out.raw\n"
    " -D                daemonize\n"
    " -h                display this help text\n"
    "Note:\n"
    " Access audio I/O through named pipes (/tmp/ec.input for playback and /tmp/ec.output for recording)\n"
    "  `cat audio.raw > /tmp/ec.input` to play audio\n"
    "  `cat /tmp/ec.output > out.raw` to get recording audio\n"
    " Only support mono playback\n";

volatile int g_is_quit = 0;

extern int fifo_setup(conf_t *conf);
extern int fifo_write(void *buf, size_t frames);

#define DENOISE_DB (-90)

extern double diff_t;

extern int rec_count;
extern int play_count;
extern int lost_pframe;
extern int lost_rframe;

extern int t_flag;

volatile int file_flag = 0;

/* Maximum attenuation of residual echo in dB (negative number) */
#define ECHO_SUPPRESS -60
/* Maximum attenuation of residual echo when near end is active, in dB (negative number) */
#define ECHO_SUPPRESS_ACTIVE -60
// static SpeexPreprocessState *st = NULL;

#define DEL_BUF_NUM 10000
void int_handler(int signal)
{
    printf("Caught signal %d, quit...\n", signal);

    g_is_quit = 1;
}
int cal_diff_count(int _pCnt, int _rCnt)
{
    if (_rCnt > _pCnt)
    {
        // capture_read(rec, (_rec - _play), timeout);
        printf("remove rec frame, %d\n", (_rCnt - _pCnt));

        // capture_read(rmBuf,(_pCnt - _rCnt)*256,1000);
    }
    if (_pCnt > _rCnt)
    {
        // playback_read(far, (_play - _rec), timeout);
        printf("remove play frame, %d\n", (_pCnt - _rCnt));
        // playback_read(rmBuf,(_pCnt - _rCnt)*256,1000);
    }
    return (_pCnt - _rCnt);
}
int cal_delay(void *buf1, void *buf2, void *ref_buf, int f_size)
{
    int check_play_flag = 0;
    int check_rec_flag = 0;
    if (memcmp(buf1, ref_buf, f_size) == 0)
    {
        check_play_flag = 1;
        // printf("aaaaa \n");
    }
    else
    {
        check_play_flag = 0;
    }
    if (memcmp(buf2, ref_buf, f_size) == 0)
    {
        check_rec_flag = 1;
        // printf("aaaaa \n");
    }
    else
    {
        check_rec_flag = 0;
    }
    if (check_play_flag && !check_rec_flag)
    {

        return 1;
    }

    return 0;
}
int cal_empty(void *buf1, void *buf2, void *ref_buf, int f_size)
{
    if (memcmp(buf2, ref_buf, f_size) == 0)
    {
        printf(".");
    }
    if (memcmp(buf1, ref_buf, f_size) == 0)
    {
        printf("_");
        return 1;
    }
    else
    {
        return 0;
        // check 127
    }
}
int run_play_cmd(FILE *pfile, char *cmdStr, int *f_flag)
{
    // pfile = popen("cat test.wav > /tmp/ec.input | cat /tmp/ec.output >recording.raw ", "r");
    if (!*f_flag)
    {
        *f_flag = 1;
        pfile = popen(cmdStr, "r");
        if (pfile == NULL)
        {
            printf("command open error \n");
            return 1;
        }

        return 0;
    }
}
int cpu_ok(void)
{
    FILE *ptr;
    int status;
    char buf[10];
    char *cpucmd = "top -b -n 1 | grep sirq | awk 'NR==1{print $8}'|cut -f 1 -d '%'";
    if ((ptr = popen(cpucmd, "r")) != NULL)
        while (fgets(buf, BUFSIZ, ptr) != NULL)
            (void)printf("%s", buf);
    status = pclose(ptr);
    printf("The exit status is: %d\n", WEXITSTATUS(status));
    int x = atoi(buf);
    printf("Converting : %d\n", x);
    if (x > 80)
    {
        return 1;
    }
    else
    {
        return 0;
    }
}
int main(int argc, char *argv[])
{
    SpeexEchoState *echo_state;
    int16_t *rec = NULL;
    int16_t *far = NULL;
    int16_t *out = NULL;
    int16_t *del_buf = NULL;
    int16_t *re_out = NULL;
    FILE *fp_rec = NULL;
    FILE *fp_far = NULL;
    FILE *fp_out = NULL;

    int opt = 0;
    int delay = 0;
    int save_audio = 0;
    int daemonize = 0;

    conf_t config = {
        .rec_pcm = "default",
        .out_pcm = "default",
        .playback_fifo = "/tmp/ec.input",
        .out_fifo = "/tmp/ec.output",
        .rate = 16000,
        .rec_channels = 1,
        .ref_channels = 1,
        .out_channels = 1,
        .bits_per_sample = 16,
        .buffer_size = 1024 * 16,
        .playback_fifo_size = 1024 * 4,
        .filter_length = 8000,
        .bypass = 0};

    while ((opt = getopt(argc, argv, "b:c:d:Df:hi:o:r:s")) != -1)
    {
        switch (opt)
        {
        case 'b':
            config.buffer_size = atoi(optarg);
            break;
        case 'c':
            config.rec_channels = atoi(optarg);
            config.out_channels = config.rec_channels;
            break;
        case 'd':
            delay = atoi(optarg);
            break;
        case 'D':
            daemonize = 1;
            break;
        case 'f':
            config.filter_length = atoi(optarg);
            break;
        case 'h':
            printf(usage, argv[0]);
            exit(0);
        case 'i':
            config.rec_pcm = optarg;
            break;
        case 'o':
            config.out_pcm = optarg;
            break;
        case 'r':
            config.rate = atoi(optarg);
            break;
        case 's':
            save_audio = 1;
            break;
        case '?':
            printf("\n");
            printf(usage, argv[0]);
            exit(1);
        default:
            break;
        }
    }

    if (daemonize)
    {
        pid_t pid, sid;

        /* Fork off the parent process */
        pid = fork();
        if (pid < 0)
        {
            printf("fork() failed\n");
            exit(1);
        }
        /* If we got a good PID, then
           we can exit the parent process. */
        if (pid > 0)
        {
            exit(0);
        }

        /* Change the file mode mask */
        umask(0);

        /* Open any logs here */

        /* Create a new SID for the child process */
        sid = setsid();
        if (sid < 0)
        {
            printf("setsid() failed\n");
            exit(1);
        }

        /* Change the current working directory */
        if ((chdir("/")) < 0)
        {
            printf("chdir() failed\n");
            exit(1);
        }
    }

    int frame_size = config.rate * 20 / 1000; // 10 ms

    if (save_audio)
    {
        fp_far = fopen("/tmp/playback.raw", "wb");
        fp_rec = fopen("/tmp/recording.raw", "wb");
        fp_out = fopen("/tmp/out.raw", "wb");

        if (fp_far == NULL || fp_rec == NULL || fp_out == NULL)
        {
            printf("Fail to open file(s)\n");
            exit(1);
        }
    }

    rec = (int16_t *)calloc(frame_size * config.rec_channels, sizeof(int16_t));
    far = (int16_t *)calloc(frame_size * config.ref_channels, sizeof(int16_t));
    out = (int16_t *)calloc(frame_size * config.out_channels, sizeof(int16_t));
    del_buf = (int16_t *)calloc(DEL_BUF_NUM, sizeof(int16_t));

    if (rec == NULL || far == NULL || out == NULL)
    {
        printf("Fail to allocate memory\n");
        exit(1);
    }

    // Configures signal handling.
    struct sigaction sig_int_handler;
    sig_int_handler.sa_handler = int_handler;
    sigemptyset(&sig_int_handler.sa_mask);
    sig_int_handler.sa_flags = 0;
    sigaction(SIGINT, &sig_int_handler, NULL);

    int i;
    float f;

    // read staus data
    // if (read_echo_staus(&echo_state))
    // {
    //     printf("Initialize the echo cancellation object\n");
    //     echo_state = speex_echo_state_init(frame_size, config.filter_length);
    // }
    // else
    // {
    //     speex_echo_state_reset(echo_state);
    // }
    SpeexPreprocessState *play_den;
    SpeexPreprocessState *rec_den;
    SpeexPreprocessState *echo_den;
    play_den = speex_preprocess_state_init(frame_size, config.rate);
    rec_den = speex_preprocess_state_init(frame_size, config.rate);
    echo_den = speex_preprocess_state_init(frame_size, config.rate);
    i = 1;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_DENOISE, &i);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_DENOISE, &i);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_DENOISE, &i);
    printf("get is noise %d\n", i);
    int noiseSuppress = DENOISE_DB;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noiseSuppress); // Set the dB of noise
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_GET_NOISE_SUPPRESS, &noiseSuppress);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noiseSuppress); // Set the dB of noise
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_GET_NOISE_SUPPRESS, &noiseSuppress);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &noiseSuppress); // Set the dB of noise
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_GET_NOISE_SUPPRESS, &noiseSuppress);

    printf("get noise DB %d\n", noiseSuppress);
    i = 0;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_AGC, &i);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_AGC, &i);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_AGC, &i);

    i = 24000;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_AGC_LEVEL, &i);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_AGC_LEVEL, &i);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_AGC_LEVEL, &i);

    i = 0;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_DEREVERB, &i);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_DEREVERB, &i);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_DEREVERB, &i);
    f = .0;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_DEREVERB_DECAY, &f);
    f = .0;
    speex_preprocess_ctl(play_den, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f);
    speex_preprocess_ctl(rec_den, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f);
    speex_preprocess_ctl(echo_den, SPEEX_PREPROCESS_SET_DEREVERB_LEVEL, &f);

    printf("Initialize the echo cancellation object\n");
    echo_state = speex_echo_state_init(frame_size, config.filter_length);
    SpeexPreprocessState *preprocess_state = speex_preprocess_state_init(frame_size, config.rate);
    speex_echo_ctl(echo_state, SPEEX_ECHO_SET_SAMPLING_RATE, &(config.rate));

    speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_ECHO_STATE, echo_state);
    i = ECHO_SUPPRESS;
    speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS, &i);
    i = ECHO_SUPPRESS_ACTIVE;
    speex_preprocess_ctl(preprocess_state, SPEEX_PREPROCESS_SET_ECHO_SUPPRESS_ACTIVE, &i);
    printf("60db....\n");

    create_mux();

    FILE *fp;
    int fp_status;

    // calculate cpu
    int cpuNum = 0;
    while (1)
    {
        int temp = cpu_ok();
        if (temp > 0)
        {
            cpuNum++;
            if (cpuNum > 2)
            {
                break;
            }
        }
        else
        {
            cpuNum = 0;
        }
        sleep(1);
    }
    FILE *echo_fd, *ref_fd;
    echo_fd = fopen("/root/playback.raw", "rb");
    ref_fd = fopen("/root/recording.raw", "rb");
    while (!feof(ref_fd) && !feof(echo_fd))
    {

        fread(rec, sizeof(short), frame_size, ref_fd);

        fread(far, sizeof(short), frame_size, echo_fd);

        speex_echo_cancellation(echo_state, rec, far, out);
        speex_preprocess_run(preprocess_state, out);

    }
    fclose(echo_fd);
    fclose(ref_fd);
    printf("calibration done...\n");

    int recNum, playNum;
    int z_num = 0;
    double els_time = 0;
    // printf("skip frames %d\n", capture_skip(delay));
    memset(far, 0, frame_size);
    memset(rec, 0, frame_size);
    memset(del_buf, 0, DEL_BUF_NUM);

    playback_start(&config);
    capture_start(&config);
    //
    fifo_setup(&config);
    while (delay > 0)
    {
        usleep(10000);
        delay--;
    }

    printf("Running... Press Ctrl+C to exit\n");

    int timeout = 200 * 1000 * frame_size / config.rate; // ms
    // system delay between recording and playback

    clock_t start = clock();


    
    while (!g_is_quit)
    {

        z_num++;

        playNum = playback_read(far, frame_size, timeout);
        speex_preprocess_run(play_den, far);

        recNum = capture_read(rec, frame_size, timeout);
        speex_preprocess_run(rec_den, rec);

        if (playNum != recNum)
        {
            printf("frame diff, %d,%d\n", playNum, recNum);
        }

        // check empty
        // int check_empty = cal_empty(far, rec, testbuf, frame_size);

        if (!file_flag && t_flag)
        {
            char *testcmd = "arecord -D hw:1,0,3 -f S16_LE -r 8000 --buffer-size=8192 -t raw  > /tmp/ec.input | aplay -D hw:1,0,4 -f S16_LE -r 8000 --buffer-size=8192 /tmp/ec.output";

            fp = popen(testcmd, "r");
            file_flag = 1;
            printf("run command....\n");
        }

        els_time = (clock() - start) / (double)(CLOCKS_PER_SEC);

        // usleep(5000);
        if (els_time > 1)
        {
            start = clock();
            // usleep(1000);
            printf("elasp_time = %f, %d, %d, %d, %d ,%d\n", els_time, z_num, play_count, rec_count, lost_pframe, lost_rframe);

            int lost_frm = lost_pframe;
            const int thr_lost = 13000;
            if (lost_frm > thr_lost)
            {
                lost_frm = lost_frm - thr_lost;
                if (lost_frm > DEL_BUF_NUM)
                {
                    printf("error delay...\n");
                }
                else
                {
                    capture_read(del_buf, lost_frm - 500, timeout);
                }
            }
            else
            {
                capture_read(del_buf, lost_frm, timeout);
            }
            z_num = 0;
            play_count = 0;
            rec_count = 0;
            lost_pframe = 0;
            lost_rframe = 0;
            // open test command
        }
        if (!config.bypass && file_flag)
        {

            speex_echo_cancellation(echo_state, rec, far, out);
            speex_preprocess_run(preprocess_state, out);
            speex_preprocess_run(echo_den, out);
            // memcpy(echo_tmp,echo_state,sizeof(echo_state));
        }
        else
        {
            memcpy(out, rec, frame_size * config.rec_channels * config.bits_per_sample / 8);
        }
        if (fp_far)
        {
            fwrite(rec, 2, frame_size * config.rec_channels, fp_rec);
            fwrite(far, 2, frame_size, fp_far);
            fwrite(out, 2, frame_size * config.out_channels, fp_out);
        }

        fifo_write(out, frame_size);
    }
    printf("end process..........\n");

    if (fp_far)
    {
        fclose(fp_rec);
        fclose(fp_far);
        fclose(fp_out);
    }
    free(del_buf);
    free(rec);
    free(far);
    free(out);

    capture_stop();
    playback_stop();
    destroy_mux();

    // save staus
    save_echo_status(echo_state);

    speex_echo_state_destroy(echo_state);
    speex_preprocess_state_destroy(preprocess_state);
    speex_preprocess_state_destroy(play_den);
    speex_preprocess_state_destroy(rec_den);
    speex_preprocess_state_destroy(echo_den);

    fp_status = pclose(fp);
    if (fp_status == -1)
    {
        printf("command close error \n");
    }

    exit(0);

    return 0;
}
int save_echo_status(SpeexEchoState *echo)
{
    FILE *outfile;
    outfile = fopen("status.dat", "w");
    if (outfile == NULL)
    {
        printf("\nError opened file\n");
        exit(1);
    }
    fwrite(echo, sizeof(echo), 1, outfile);
    if (fwrite != 0)
    {
        printf("contents successfully!\n");
    }
    else
    {
        printf("error writing\n");
    }
    fclose(outfile);
    return 0;
}
int read_echo_staus(SpeexEchoState *_echo)
{
    FILE *infile;
    infile = fopen("status.dat", "r");
    if (infile == NULL)
    {
        printf("\nError opening file\n");
        return 1;
    }
    // read file contents till end of file
    while (fread(_echo, sizeof(_echo), 1, infile))
        printf("read file.....\n");
    fclose(infile);
    return 0;
}